<?php
class Pageley_Config_Reference {
	private $config;
	private $id;
	private $factory;
	private $instance;
	private $logger;
	private $constructing;
    private $singleton;

	public function __construct($id, $config = null, $factory = null, $instance = null) {
		$this->id = $id;
		$this->config = $config;
        $this->singleton = true;
        if($config !== null && array_key_exists("singleton", $config)) {
            $this->singleton = $config["singleton"] == "true";
        }
		$this->factory = $factory;
        $this->instance = $instance;
		$this->constructing = false;
	}

	private function getLogger() {
		if($this->logger === null) {
			$logFactory = Pageley_Log_Factory::getInstance();
			$this->logger = $logFactory->getLogger($this);
		}
		return $this->logger;
	}

	public function getInstance($constructorArguments) {
		$logger = $this->getLogger();
		$config = $this->config;
		if(!$this->singleton || $this->instance === null) {
            if($constructorArguments === null) {
                $constructorArguments = new Pageley_Util_ArrayMap();
            }
			$this->constructing = true;
			$start = null;
			if($logger->isTraceEnabled()) {
				$logger->trace("Creating new instance for '" . $this->id . "'.");
				$start = new Pageley_Util_Timestamp();
			}
            if(!array_key_exists("class", $config)) {
                throw new Pageley_Config_Exception("Missing required parameter 'class' for '" . $this->id . "'.");
            }
			$className = $config["class"];
			$factoryMethod = null;
			if(array_key_exists("constructor", $config)) {
			    $constructorArguments->addAll($this->getConstructorArguments($config["constructor"]));
			} else if(array_key_exists("factoryMethod", $config)) {
				$factoryMethod = $config["factoryMethod"];
			}
			try {
				if($factoryMethod === null) {
					$this->instance = Pageley_Util_Reflection::newInstance($className, $constructorArguments->toArray());
				} else {
					$this->instance = Pageley_Util_Reflection::invokeStaticMethod($className, $factoryMethod);
				}
				$this->constructing = false;
				foreach($config as $name => $value) {
					if($name == "properties") {
						$this->setProperties($this->instance, $value);
					}
				}
			} catch(Pageley_Util_NoSuchMethodException $e) {
				throw new Pageley_Config_Exception("No setter found for property '" . $name . "' on class '" . $className . "' for '" . $this->id . "'.", 0, $e);
			} catch(Pageley_Util_NoSuchClassException $e) {
				throw new Pageley_Config_Exception("No class found with name '" . $className . "' for '" . $this->id . "'.", 0, $e);
			}
			if($logger->isTraceEnabled()) {
				$end = new Pageley_Util_Timestamp();
				$time = $end->getTime() - $start->getTime();
				$logger->trace("Finished creating instance for '" . $this->id . "' in " . $time . "ms.");
			}
		} else if($logger->isTraceEnabled()) {
			$logger->trace("Returning existing instance for '" . $this->id . "'.");
		}
		return $this->instance;
	}

	private function setProperties($instance, $properties) {
		foreach($properties as $name => $value) {
			Pageley_Util_Reflection::setProperty($instance, $name, $this->resolveValue($value));
		}
	}

	private function getConstructorArguments($argumentsConfig) {
		$arguments = new Pageley_Util_ArrayMap();
		foreach($argumentsConfig as $name => $value) {
			$arguments->add($this->resolveValue($value));
		}
		return $arguments->toArray();
	}

	private function resolveValue($value) {
		$object = null;
		if(is_array($value)) {
			$object = new Pageley_Util_ArrayMap();
			foreach($value as $key => $val) {
				$object->put($key, $this->resolveValue($val));
			}
		} else {
            $matches = array();
            if(preg_match_all("/^\#{([\\w().]+)}\$/", $value, $matches)) {
                $object = $this->factory->valueOf($matches[1][0]);
            } else {
                $object = $value;
            }
		}
		return $object;
	}

	public function isConstructing() {
		return $this->constructing;
	}
}